"""
mod_python authentication

Usage:

In httpd.conf, you should add these lines:

    LoadModule python_module libexec/apache2/mod_python.so

    # Be sure this script is accessible through one of these paths:
    #   (/usr/local/lib/python2.3 is also required)
    PythonPath "['/usr/local/lib/python2.3', '/usr/local/lib/python2.3/plat-freebsd4', '/usr/local/lib/python2.3/lib-dynload', '/usr/local/lib/python2.3/site-packages', '/usr/local/lib/python2.3/site-packages/Numeric', '/usr/local/lib/python2.3/site-packages/PIL']"
    # If you want to test stuff:
    #PythonDebug on
    PythonHeaderParserHandler path.to.authen
    PythonOption login_screen /cgi-bin/login.cgi
    PythonOption login_required yes


You can use 'PythonOption login_required yes' in a <Directory> or
<Location>.  The login_screen can be set locally or globally.  The
screen can be a CGI script, Zope page, or whatever.  It needs only
set the appropriate cookie on successful login.

This also provides the REMOTE_USER variable.  Unlike HTTP Basic
authentication, you do not have to reject an anonymous user before
the user will appear logged in (and two requests will not have to
be made).  This way unprotected pages can see what the current user
is (or '' if no user).

This cannot catch 401 errors, so unauthorized errors will cause
problems with this script.  Unfortunately while you can set
ErrorDocument 401 /cgi-bin/login.cgi, the user will only see that
screen after they cancel the Basic login.  They will not be able
to login through the Basic login popup, causing much confusion.


You can also import this module for use in the login screen.  The
two applicable functions in that case are
authorize_user_cookie(username) and unauthorize_user_cookie().
"""

try:
    from mod_python import apache
    from mod_python import Cookie
except ImportError:
    # this happens if you use this outside of mod_python, which
    # is okay.
    pass
import md5
import time

# @@: This shouldn't be hardcoded; but including it only in the Apache
# config makes it hard for programs outside of Apache to access the
# secret.  It probably should be kept in a file.  Security of such a
# file is important, but it's not very important to protect such a
# file from a local exploit.
SECRET = 'myspecialsecret'

def headerparserhandler(req):
    """
    The actual authenticating check.  This checks if there is a proper
    cookie in the request, and sets the request based on that.  It
    also clears out any attempt to do HTTP Basic authentication.

    The cookie should look like sig:user_enc, where user_enc is the
    username encoded in base64.  The signature is the secret (SECRET
    at the top of this file) concatenated with the unencoded username.
    """
    # First we clear out any stale login information; we are in
    # this process disallowing HTTP Basic login, because we are
    # actually avoiding doing any password checking here.
    req.user = ''
    cookies = Cookie.get_cookies(req)
    if cookies.has_key('authinfo'):
        authinfo = cookies['authinfo'].value
        # We decode the cookie and confirm the signature,
        # lastly setting the username.
        try:
            if ':' not in authinfo:
                raise ValueError, "Badly formatted cookie"
            sig, username = authinfo.split(':', 1)
            username = username.decode('base64')
            if md5.new(SECRET + username).hexdigest() != sig:
                raise ValueError, "Bad cookie signature"
            req.user = username
        except ValueError, msg:
            req.user = ''
    # We get rid of any spurious Authorization headers, and
    # put in a fake authorization header if there is a user
    # logged in.
    if req.user:
        req.headers_in['Authorization'] = (
            'Basic ' + (req.user  + ':').encode('base64'))
    else:
        if req.headers_in.has_key('Authorization'):
            del req.headers_in['Authorization']

    return check_authorization(req)

def check_authorization(req):
    """
    Checks if the user is authorized.  Currently the only restriction
    is PythonOption login_required yes; there's no other ways to
    restrict users.
    """
    ops = req.get_options()
    if (ops.get('login_required')
        and ops['login_required'].lower().startswith('y')):
        if not req.user:
            redir = ops.get('login_screen')
            if redir:
                req.internal_redirect(redir)
                return apache.OK
            else:
                return apache.HTTP_UNAUTHORIZED
    return apache.OK

def authorize_user_cookie(username):
    """
    Use like:
    print 'Set-Cookie: ' + authorize_user_cookie('bob')
    """
    sig = md5.new(SECRET + username).hexdigest()
    return 'authinfo=%s:%s; Path=/;' % (sig, username.encode('base64').strip())

def unauthorize_user_cookie():
    """
    Used for logout, like:
    print 'Set-Cookie: ' + unauthorize_user_cookie()
    """
    return ('authinfo=; Path=/; expires=%s' %
            time.strftime(time.gmtime(),
                          '%a, %d-%b-%Y %H:%M:%S GMT'))

    
def authen(req):
   test.test
   return apache.HTTP_UNAUTHORIZED







